/*
 * Created by Spear (http://www.codernet.es), spear@codernet.es
 * Coordinator of XNA Community (http://www.codeplex.com/XNACommunity)
 * 
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;

using XMas;


namespace XMasContent
{
  public class CMD5AnimParser
  {
    int num_frames;
    int num_joints;
    int frame_rate;
    int num_animated_components;

    class SHierarchyInfo
    {
      public string name;
      public int parent;
      public int flags;
      public int startIndex;
    };

    class SBounds
    {
      public Vector3 min;
      public Vector3 max;
    };

    class SBaseframe
    {
      public Vector3 pos;
      public Vector3 oriented;
    };

    class SFrameInfo
    {
      public List<float> info = new List<float>();
    };

    List<SHierarchyInfo> list_hierarchy = new List<SHierarchyInfo>();
    List<SBounds> list_bounds = new List<SBounds>();
    List<SBaseframe> list_baseframes = new List<SBaseframe>();
    List<SFrameInfo> list_frames = new List<SFrameInfo>();

    CSkeletonData skeleton_data = null;
    CSkeletonData.CAnimationData current_animation = null;

    public void load(string filename, CSkeletonData s) {
      skeleton_data = s;
      CMD5Parser parser = new CMD5Parser(filename);
      parser.seekToNextKey("MD5Version");
      int md5_version = (int)parser.getNumber();
      if (md5_version == 10)
      {
        parser.seekToNextKey("numFrames");
        num_frames = (int)parser.getNumber();
        parser.seekToNextKey("numJoints");
        num_joints = (int)parser.getNumber();
        parser.seekToNextKey("frameRate");
        frame_rate = (int)parser.getNumber();
        parser.seekToNextKey("numAnimatedComponents");
        num_animated_components = (int)parser.getNumber();
        parser.seekToNextKey("hierarchy");
        for (int i = 0; i < num_joints; ++i)
        {
          SHierarchyInfo hierarchy = new SHierarchyInfo();
          hierarchy.name = parser.getString();
          hierarchy.parent = (int)parser.getNumber();
          hierarchy.flags = (int)parser.getNumber();
          hierarchy.startIndex = (int)parser.getNumber();
          list_hierarchy.Add(hierarchy);
        }
        parser.seekToNextKey("bounds");
        for (int i = 0; i < num_frames; ++i)
        {
          SBounds bound = new SBounds();
          bound.min = parser.getVector3();
          bound.max = parser.getVector3();
          list_bounds.Add(bound);
        }
        parser.seekToNextKey("baseframe");
        for (int i = 0; i < num_joints; ++i)
        {
          SBaseframe baseframe = new SBaseframe();
          baseframe.pos = parser.getVector3();
          baseframe.oriented = parser.getVector3();
          list_baseframes.Add(baseframe);
        }
        for (int i = 0; i < num_frames; ++i)
        {
          parser.seekToNextKey("frame");
          SFrameInfo frame_info = new SFrameInfo();
          int frame_id = (int)parser.getNumber();
          while (!parser.isEOFKey())
          {
            frame_info.info.Add(parser.getNumber());
          }
          list_frames.Add(frame_info);
        }
        createSkeleton(Path.GetFileNameWithoutExtension(filename).ToLower());
      }
      else
      {
        throw new InvalidContentException("MD5 Version invalid! only support version 10!");
      }
    }

    private CSkeletonData.CFramesData createSkeletonByFrame(int frame_index)
    {
      CSkeletonData.CFramesData frame_data = new CSkeletonData.CFramesData();
      SFrameInfo frame_info = list_frames[frame_index];
      Quaternion q = Quaternion.CreateFromAxisAngle(new Vector3(0.0f, 1.0f, 0.0f), MathHelper.PiOver2);
      q = q * Quaternion.CreateFromAxisAngle(new Vector3(1.0f, 0.0f, 0.0f), MathHelper.PiOver2);
      q = q * Quaternion.CreateFromAxisAngle(new Vector3(0.0f, 1.0f, 0.0f), MathHelper.Pi);
      q.Normalize();

      for (int i = 0; i < list_hierarchy.Count; ++i)
      {
        SBaseframe base_frame = list_baseframes[i];
        Vector3 pos = base_frame.pos;
        Quaternion oriented = new Quaternion(); //w is empty
        oriented.X = base_frame.oriented.X; 
        oriented.Y = base_frame.oriented.Y;
        oriented.Z = base_frame.oriented.Z;

        SHierarchyInfo hierarchy = list_hierarchy[i];
        int index_flag = 0;

        if ((hierarchy.flags & 1) == 1) 
        { /* Tx */
          pos.X = frame_info.info[hierarchy.startIndex + index_flag];
          ++index_flag;
        }
        if ((hierarchy.flags & 2) == 2)
        { /* Ty */
          pos.Y = frame_info.info[hierarchy.startIndex + index_flag];
          ++index_flag;
        }
        if ((hierarchy.flags & 4) == 4)
        { /* Tz */
          pos.Z = frame_info.info[hierarchy.startIndex + index_flag];
          ++index_flag;
        }
        if ((hierarchy.flags & 8) == 8)
        { /* Qx */
          oriented.X = frame_info.info[hierarchy.startIndex + index_flag];
          ++index_flag;
        }
        if ((hierarchy.flags & 16) == 16)
        { /* Qy */
          oriented.Y = frame_info.info[hierarchy.startIndex + index_flag];
          ++index_flag;
        }
        if ((hierarchy.flags & 32) == 32)
        { /* Qz */
          oriented.Z = frame_info.info[hierarchy.startIndex + index_flag];
          ++index_flag;
        }

        //compute w value
        float t = 1.0f - (oriented.X * oriented.X) - (oriented.Y * oriented.Y) - (oriented.Z * oriented.Z);
        if (t <= 0.0f)
        {
          oriented.W = 0.0f;
        }
        else
        {
          oriented.W = -(float)Math.Sqrt(t);
        }

        oriented.Normalize();
        /*
         * We assume that this joint's parent has
         * already been calculated.
        */
        int parent = hierarchy.parent;
        CSkeletonData.CSkeletonNode node = new CSkeletonData.CSkeletonNode();
        node.parent = parent;
        if (parent < 0) //This joint hasn't parent
        {
          node.pos = Vector3.Transform(pos, q);
          node.oriented = oriented * q;
          //node.oriented = oriented;
          node.oriented.Normalize();
        }
        else //This joint has parent
        {
          node.pos = pos;
          node.oriented = oriented;
          node.oriented.Normalize();
        }
        frame_data.list_skeleton.Add(node);
      }
      CSkeletonData.CSkeletonNode node_center;
      node_center = frame_data.list_skeleton[0];
        // TODO: Deleted
        /*
      frame_data.bounding_box = new BoundingBox();
      frame_data.bounding_box.Min = list_bounds[frame_index].min;
      frame_data.bounding_box.Max = list_bounds[frame_index].max;
      frame_data.bounding_box.Min = Vector3.Transform(frame_data.bounding_box.Min, q) - node_center.pos;
      frame_data.bounding_box.Max = Vector3.Transform(frame_data.bounding_box.Max, q) - node_center.pos;
         */
      return frame_data;
    }

    private void createSkeleton(string name)
    {
      //create animation entry
      current_animation = new CSkeletonData.CAnimationData();
      //list_frames
      for (int i = 0; i < list_frames.Count; ++i) 
      {
        CSkeletonData.CFramesData frame = createSkeletonByFrame(i);
        current_animation.list_frames.Add(frame);
      }
      current_animation.frame_rate = frame_rate;
      skeleton_data.addAnimation(name, current_animation);
    }
  }
}
